"""

"""

import sys
import thread
import threading
import traceback

from coffeetalk.protocol import access, ioml, requests, responses, iotypes
from coffeetalk.server import ionode, iotree
from coffeetalk.utilities import iologger

class IOUser:
    
    CONNECTING = 0
    AUTHENTICATING = 1
    LOGGEDIN = 2
    
    def __init__(self,connection,iotrees,userclass='Users'):
        """ Initialize our IOUser 
            
            Parameters:
            connection - a valid Connection object such as asyncconnection.IOAsyncConnection
        """
        
        self.log = connection.log
        self.connection = connection
        self.userclass = userclass
        
        self.ip = connection.ip
        self.username = '""'
        self.group = '""'
        self.connectionid = str(connection)[1:-1]
        self.requestid = 0
        
        self.subscriptions = {}
        self.anchorsubscriptions = {}
        self.monitors = {}
        
        self.subscriptionlock = thread.allocate_lock()
        self.anchorsubscriptionlock = thread.allocate_lock()
        self.monitorlock = thread.allocate_lock()    
        
        self.status = IOUser.CONNECTING
        
        self.message_handler = self._handle_login
        self.env = None
        self.extension = None
        
        self.iotrees = iotrees
        self.emptytree = iotree.NullIOTree()
        self._add_connecting_node()
        
    def _find_iotree(self,path):
        try:
            root = path.split('/')[1]
            return self.iotrees[root]
        except:
            return self.emptytree

    def add_subscription(self,path):
        self.subscriptionlock.acquire()
        self.subscriptions[path] = '1'
        self.subscriptionlock.release()
    
    def remove_subscription(self,path):
        self.subscriptionlock.acquire()
        if self.subscriptions.has_key(path):
            del self.subscriptions[path]
        self.subscriptionlock.release()
    
    def add_anchor_subscription(self,path):
        self.anchorsubscriptionlock.acquire()
        self.anchorsubscriptions[path] = True
        self.anchorsubscriptionlock.release()
        
    def remove_anchor_subscription(self,path):
        self.anchorsubscriptionlock.acquire()
        if self.anchorsubscriptions.has_key(path):
            del self.anchorsubscriptions[path]
        self.anchorsubscriptionlock.release()
        
    def add_monitor(self,path,monitortype):
        #TODO: must make monitors work correctly
        self.monitorlock.acquire()
        if not self.monitors.has_key(path):
            self.monitors[path] = {}
        if not self.monitors[path].has_key(monitortype):
            self.monitors[path][monitortype] = '1'
        self.monitorlock.release()
        
    def remove_monitor(self,path,monitortype):
        self.monitorlock.acquire()
        if self.monitors.has_key(path) and self.monitors[path].has_key(monitortype):
            del self.monitors[path][monitortype]
        self.monitorlock.release()       
        
    def close(self):
            
        removethread = None
        if self.status == IOUser.LOGGEDIN:
            removethread = threading.Thread(target=self._remove_connected_node)
        else:
            removethread = threading.Thread(target=self._remove_connecting_node)
        removethread.setName('Remove Thread: ' + str(self))
        removethread.start()
                
    def _remove_connected_node(self):
        env = iotypes.Environment(self.ip,self.username,'Internal',self.connectionid,str(self.requestid))
        self._find_iotree('/System/').delete_node(env,'/System/Network/Clients/' + str(self.userclass) + '/Connected/',self.connectionid)
        
        if self.extension is not None:
            self._find_iotree(self.extension).remove_extension_client(self.env,self.extension,self)
        
        self._remove_subscriptions()
    
    def _remove_subscriptions(self):
        self.subscriptionlock.acquire()
        for key in self.subscriptions.keys():
            response = self._find_iotree(key).delete_subscription(self.env,key,str(self.connection.send_message))
            if isinstance(response,responses.Accepted):
                request = requests.Unsubscribe(path=key)
                try:
                    response.handler(self.env,ioml.IOML(iomlstring=str(request)),self)
                except:
                    traceback.print_exc()
                    self.log.log_error(iologger.DEBUG,'0.0.0.0',self.username,self.group,iologger.current_function(),'InternalError',str(response.handler))
                        
        self.subscriptionlock.release()
        
    def _add_connecting_node(self):
        connectingnode = ionode.IONode(self.connectionid,{})
        env = iotypes.Environment(self.ip,'Connecting','Internal',self.connectionid,str(self.requestid))
        self._find_iotree('/System/').add_node(env,'/System/Network/Clients/Connecting/',connectingnode)
        self._find_iotree('/System/').add_subscription(env,'/System/Network/Clients/Connecting/' + str(self.connectionid) + '/',self._handle_loginresponse)
             
    def _remove_connecting_node(self):
        env = iotypes.Environment(self.ip,self.username,'Internal',self.connectionid,str(self.requestid))
        if self.status == IOUser.AUTHENTICATING:
            self._find_iotree('/System/').delete_node(env,'/System/Network/Clients/' + str(self.userclass) + '/Login/',self.connectionid)
        self._find_iotree('/System/').delete_node(env,'/System/Network/Clients/Connecting/',self.connectionid)
        
    def _add_connected_node(self):
        
        connectednode = ionode.IONode(self.connectionid,{})
        env = iotypes.Environment(self.ip,self.username,'Internal',self.connectionid,'0')
        self._find_iotree('/System/').add_node(env,'/System/Network/Clients/' + str(self.userclass) + '/Connected/',connectednode)
        
        commandnode = ionode.IONode('Commands',{'type':'[Stream]'})
        self._find_iotree('/System/').add_node(env,'/System/Network/Clients/' + str(self.userclass) + '/Connected/' + str(self.connectionid) + '/',commandnode)
        self._find_iotree('/System/').add_subscription(env,'/System/Network/Clients/' + str(self.userclass) + '/Connected/' + str(self.connectionid) + '/',self._handle_command)
        
    def _handle_login(self,message):
        iomessage = ioml.IOML(iomlstring=message)
        try:
            self.username = iomessage.headelement.attribs['username']
            password = iomessage.headelement.attribs['password']
            group = iomessage.headelement.attribs['group']
            credentials = iotypes.Credentials(name=self.connectionid,username=self.username,password=password,group=group,ip=self.ip)
            clienttype = iomessage.headelement.attribs['clienttype']
            if clienttype == 'ActionScript':
                self.connection.nullbyte = True
            env = iotypes.Environment(self.ip,self.username,'Internal',self.connectionid,str(self.requestid))
            self.status = IOUser.AUTHENTICATING
            self._find_iotree('/System/').add_node(env,'/System/Network/Clients/' + str(self.userclass) + '/Login/',credentials._to_element())
        except:
            type,value = sys.exc_info()[:2]
            self.log.log_error(iologger.ERROR,self.ip,self.username,self.group,iologger.current_function(),'InternalError',str(type) + ':' + str(value))
            
    def _handle_loginresponse(self,loginresponse):
        
        iomessage = ioml.IOML(iomlstring=loginresponse)
        if iomessage.bodyelement.attribs['type'] == '[Authorized]':
            self.username = iomessage.bodyelement.attribs['username']
            self.group = iomessage.bodyelement.attribs['group']
            
            self.message_handler = self.process_message
            
            removethread = threading.Thread(target=self._remove_connecting_node)
            removethread.setName('Remove Thread: ' + str(self))
            removethread.start()
            self.status = IOUser.LOGGEDIN

            addthread = threading.Thread(target=self._add_connected_node)
            addthread.setName('Add Thread: ' + str(self))
            addthread.start()
            
            response = responses.Authorized(self.env,path='[Login]')
            self.env = iotypes.Environment(self.ip,self.username,self.group,self.connectionid,str(self.requestid))
            self.connection.send_message(str(response))
        else:
            self.log.log_error(iologger.WARNING,self.ip,self.username,self.group,iologger.current_function(),'Unauthorized','Bad Login')
            response = responses.Unauthorized(requestid=str(id(self)),path="[Login]")
            self.connection.send_message(str(response))
            self.connection.handle_close(reason='Bad Login')
    
    def _handle_command(self,message):
        pass
    
    def process_message(self,message):
        
        request = ioml.IOML(iomlstring=message,nodetype=ionode.IONode)
        try:
            if request.headelement.name != 'Request':
                raise AttributeError('Unknown request: %s' % request.headelement.name)
            
            handler = request.headelement.attribs['type']
            requestid = request.headelement.attribs['requestid']
            path = request.headelement.attribs['path']
            env = iotypes.Environment(self.ip,self.username,self.group,self.connectionid,requestid)
            
            response = getattr(self,'handle_' + handler)(request,env,path)
            if response is not None:
                if isinstance(response,responses.Accepted):
                    try:
                        response.handler(env,request,self)
                    except:
                        self.log.log_error(iologger.DEBUG,'0.0.0.0',self.username,self.group,iologger.current_function(),'InternalError',str(response.handler))
                        raise
                else:
                    self.connection.send_message(str(response))
        except:
            type,value = sys.exc_info()[:2]
            self.log.log_error(iologger.DEBUG,'0.0.0.0',self.username,self.group,iologger.current_function(),'InternalError',str(type) + ':' + str(value))
            self.log.log_error(iologger.ERROR,self.ip,self.username,self.group,iologger.current_function(),'InternalError','Unknown request: %s' % request.headelement.name)
            
    def handle_Get(self,request,env,path):
        return self._find_iotree(path).get_node(env, path)
      
    def handle_Add(self,request,env,path):
        return self._find_iotree(path).add_node(env,path,request.bodyelement)

    def handle_Delete(self,request,env,path):
        key = request.headelement.attribs['key']
        return self._find_iotree(path).delete_node(env,path,key)
        
    def handle_Subscribe(self,request,env,path):
        response = self._find_iotree(path).add_subscription(env,path,self.connection.send_message)
        if isinstance(response,responses.OK):
            self.add_subscription(path)
        return response
              
    def handle_Unsubscribe(self,request,env,path):
        response = self._find_iotree(path).delete_subscription(env,path,str(self.connection.send_message))
        if isinstance(response,responses.OK):
            self.remove_subscription(path)
        return response
    
    def handle_AddMonitor(self,request,env,path):
        monitortype = request.headelement.attribs['monitortype']
        response = self._find_iotree(path).add_monitor(env,path,monitortype,self.connection.send_message)
        if isinstance(response,responses.OK):
            self.add_monitor(path, monitortype)
        return response
        
    def handle_DeleteMonitor(self,request,env,path):
        monitortype = request.headelement.attribs['monitortype']
        response = self._find_iotree(path).delete_monitor(env,path,monitortype,str(self.connection.send_message))
        if isinstance(response,responses.OK):
            self.remove_monitor(path, monitortype)
        return response
    
    def handle_AddAccess(self,request,env,path):
        user = request.headelement.attribs['username']
        group = request.headelement.attribs['group']
        permissions = request.headelement.attribs['permissions']
        
        accessobject = None
        if user != '' and group != '':
            accessobject = access.Simple(user=user,group=group,permissions=permissions)            
        elif user != '':
            accessobject = access.User(name=user,permissions=permissions)
        elif group != '':
            accessobject = access.Group(name=group,permissions=permissions)
        else:
            accessobject = access.Others(permissions=permissions)
        
        return self._find_iotree(path).add_access(env,path,accessobject)
    
    def handle_RemoveAccess(self,request,env,path):
        accesskey = request.headelement.attribs['accesskey']
        return self._find_iotree(path).remove_access(env,path,accesskey)
    
    def handle_AddExtension(self,request,env,path):
        try:
            response = self._find_iotree(path).add_extension_client(env,path,self)
            if isinstance(response,responses.OK):
                self.extension = path
            return response
        except:
            import traceback
            traceback.print_exc()
            return responses.InternalError(env=env,path=path,err='InternalError')
        
    def handle_RemoveExtension(self,request,env,path):
        response = self._find_iotree(path).remove_extension_client(env,path,self)
        if isinstance(response,responses.OK):
            self.extension = None
        return response
    
    def handle_AddAnchor(self,request,env,path):
        response = self._find_iotree(path).add_anchor(env,path)
        if isinstance(response,responses.OK):
            self.add_anchor_subscription(path)
        return response
    
    def handle_RemoveAnchor(self,request,env,path):
        response = self._find_iotree(path).remove_anchor(env,path)
        if isinstance(response,responses.OK):
            self.remove_anchor_subscription(path)
        return response
      
        